﻿/*
 * 
 * An XmlReader implementation for loading SGML (including HTML) converting it
 * to well formed XML, by adding missing quotes, empty attribute values, ignoring
 * duplicate attributes, case folding on tag names, adding missing closing tags
 * based on SGML DTD information, and so on.
 *
 * Copyright (c) 2002 Microsoft Corporation. All rights reserved. (Chris Lovett)
 *
 */

/*
 * 
 * Copyright (c) 2007-2013 MindTouch. All rights reserved.
 * www.mindtouch.com  oss@mindtouch.com
 *
 * For community documentation and downloads visit wiki.developer.mindtouch.com;
 * please review the licensing section.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/*
 * SgmlReader for Portable 
 * Copyright (c) 2014 Kouji Matsui, All rights reserved.
 * http://sgmlreaderportable.codeplex.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;

namespace Sgml
{
	/// <summary>
	/// SGML is case insensitive, so here you can choose between converting
	/// to lower case or upper case tags.  "None" means that the case is left
	/// alone, except that end tags will be folded to match the start tags.
	/// </summary>
	public enum CaseFolding
	{
		/// <summary>
		/// Do not convert case, except for converting end tags to match start tags.
		/// </summary>
		None,

		/// <summary>
		/// Convert tags to upper case.
		/// </summary>
		ToUpper,

		/// <summary>
		/// Convert tags to lower case.
		/// </summary>
		ToLower
	}

	/// <summary>
	/// This stack maintains a high water mark for allocated objects so the client
	/// can reuse the objects in the stack to reduce memory allocations, this is
	/// used to maintain current state of the parser for element stack, and attributes
	/// in each element.
	/// </summary>
	internal class HWStack
	{
		private object[] m_items;
		private int m_size;
		private int m_count;
		private int m_growth;

		/// <summary>
		/// Initialises a new instance of the HWStack class.
		/// </summary>
		/// <param name="growth">The amount to grow the stack space by, if more space is needed on the stack.</param>
		public HWStack(int growth)
		{
			this.m_growth = growth;
		}

		/// <summary>
		/// The number of items currently in the stack.
		/// </summary>
		public int Count
		{
			get
			{
				return this.m_count;
			}
			set
			{
				this.m_count = value;
			}
		}

		/// <summary>
		/// The size (capacity) of the stack.
		/// </summary>
		[SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
		public int Size
		{
			get
			{
				return this.m_size;
			}
		}

		/// <summary>
		/// Returns the item at the requested index or null if index is out of bounds
		/// </summary>
		/// <param name="i">The index of the item to retrieve.</param>
		/// <returns>The item at the requested index or null if index is out of bounds.</returns>
		public object this[int i]
		{
			get
			{
				return (i >= 0 && i < this.m_size) ? m_items[i] : null;
			}
			set
			{
				this.m_items[i] = value;
			}
		}

		/// <summary>
		/// Removes and returns the item at the top of the stack
		/// </summary>
		/// <returns>The item at the top of the stack.</returns>
		public object Pop()
		{
			this.m_count--;
			if (this.m_count > 0)
			{
				return m_items[this.m_count - 1];
			}

			return null;
		}

		/// <summary>
		/// Pushes a new slot at the top of the stack.
		/// </summary>
		/// <returns>The object at the top of the stack.</returns>
		/// <remarks>
		/// This method tries to reuse a slot, if it returns null then
		/// the user has to call the other Push method.
		/// </remarks>
		public object Push()
		{
			if (this.m_count == this.m_size)
			{
				int newsize = this.m_size + this.m_growth;
				object[] newarray = new object[newsize];
				if (this.m_items != null)
					Array.Copy(this.m_items, newarray, this.m_size);

				this.m_size = newsize;
				this.m_items = newarray;
			}
			return m_items[this.m_count++];
		}

		/// <summary>
		/// Remove a specific item from the stack.
		/// </summary>
		/// <param name="i">The index of the item to remove.</param>
		[SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
		public void RemoveAt(int i)
		{
			this.m_items[i] = null;
			Array.Copy(this.m_items, i + 1, this.m_items, i, this.m_count - i - 1);
			this.m_count--;
		}
	}

	/// <summary>
	/// This class represents an attribute.  The AttDef is assigned
	/// from a validation process, and is used to provide default values.
	/// </summary>
	internal class Attribute
	{
		internal string Name;    // the atomized name.
		internal AttDef DtdType; // the AttDef of the attribute from the SGML DTD.
		internal char QuoteChar; // the quote character used for the attribute value.
		private string m_literalValue; // the attribute value

		/// <summary>
		/// Attribute objects are reused during parsing to reduce memory allocations, 
		/// hence the Reset method.
		/// </summary>
		public void Reset(string name, string value, char quote)
		{
			this.Name = name;
			this.m_literalValue = value;
			this.QuoteChar = quote;
			this.DtdType = null;
		}

		public string Value
		{
			get
			{
				if (this.m_literalValue != null)
					return this.m_literalValue;
				if (this.DtdType != null)
					return this.DtdType.Default;
				return null;
			}
			/*            set
						{
							this.m_literalValue = value;
						}*/
		}

		public bool IsDefault
		{
			get
			{
				return (this.m_literalValue == null);
			}
		}
	}

	/// <summary>
	/// This class models an XML node, an array of elements in scope is maintained while parsing
	/// for validation purposes, and these Node objects are reused to reduce object allocation,
	/// hence the reset method.  
	/// </summary>
	internal class Node
	{
		internal XmlNodeType NodeType;
		internal string Value;
		internal XmlSpace Space;
		internal string XmlLang;
		internal bool IsEmpty;
		internal string Name;
		internal ElementDecl DtdType; // the DTD type found via validation
		internal State CurrentState;
		internal bool Simulated; // tag was injected into result stream.
		HWStack attributes = new HWStack(10);

		/// <summary>
		/// Attribute objects are reused during parsing to reduce memory allocations, 
		/// hence the Reset method. 
		/// </summary>
		public void Reset(string name, XmlNodeType nt, string value)
		{
			this.Value = value;
			this.Name = name;
			this.NodeType = nt;
			this.Space = XmlSpace.None;
			this.XmlLang = null;
			this.IsEmpty = true;
			this.attributes.Count = 0;
			this.DtdType = null;
		}

		public Attribute AddAttribute(string name, string value, char quotechar, bool caseInsensitive)
		{
			Attribute a;
			// check for duplicates!
			for (int i = 0, n = this.attributes.Count; i < n; i++)
			{
				a = (Attribute)this.attributes[i];
				if (string.Equals(a.Name, name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
				{
					return null;
				}
			}
			// This code makes use of the high water mark for attribute objects,
			// and reuses exisint Attribute objects to avoid memory allocation.
			a = (Attribute)this.attributes.Push();
			if (a == null)
			{
				a = new Attribute();
				this.attributes[this.attributes.Count - 1] = a;
			}
			a.Reset(name, value, quotechar);
			return a;
		}

		[SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Kept for potential future usage.")]
		public void RemoveAttribute(string name)
		{
			for (int i = 0, n = this.attributes.Count; i < n; i++)
			{
				Attribute a = (Attribute)this.attributes[i];
				if (string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase))
				{
					this.attributes.RemoveAt(i);
					return;
				}
			}
		}
		public void CopyAttributes(Node n)
		{
			for (int i = 0, len = n.attributes.Count; i < len; i++)
			{
				Attribute a = (Attribute)n.attributes[i];
				Attribute na = this.AddAttribute(a.Name, a.Value, a.QuoteChar, false);
				na.DtdType = a.DtdType;
			}
		}

		public int AttributeCount
		{
			get
			{
				return this.attributes.Count;
			}
		}

		public int GetAttribute(string name)
		{
			for (int i = 0, n = this.attributes.Count; i < n; i++)
			{
				Attribute a = (Attribute)this.attributes[i];
				if (string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase))
				{
					return i;
				}
			}
			return -1;
		}

		public Attribute GetAttribute(int i)
		{
			if (i >= 0 && i < this.attributes.Count)
			{
				Attribute a = (Attribute)this.attributes[i];
				return a;
			}
			return null;
		}
	}

	internal enum State
	{
		Initial,    // The initial state (Read has not been called yet)
		Markup,     // Expecting text or markup
		EndTag,     // Positioned on an end tag
		Attr,       // Positioned on an attribute
		AttrValue,  // Positioned in an attribute value
		Text,       // Positioned on a Text node.
		PartialTag, // Positioned on a text node, and we have hit a start tag
		AutoClose,  // We are auto-closing tags (this is like State.EndTag), but end tag was generated
		CData,      // We are on a CDATA type node, eg. <scipt> where we have special parsing rules.
		PartialText,
		PseudoStartTag, // we pushed a pseudo-start tag, need to continue with previous start tag.
		Eof
	}


	/// <summary>
	/// SgmlReader is an XmlReader API over any SGML document (including built in 
	/// support for HTML).  
	/// </summary>
	public class SgmlReader : XmlReader
	{
		/// <summary>
		/// The value returned when a namespace is queried and none has been specified.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1705", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions.  Visually, constants using this style are more easily identifiable as such.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707", Justification = "SgmlReader's standards for constants are different to Microsoft's and in line with older C++ style constants naming conventions.  Visually, constants using this style are more easily identifiable as such.")]
		public const string UNDEFINED_NAMESPACE = "#unknown";

		private SgmlDtd m_dtd;
		private Entity m_current;
		private State m_state;
		private char m_partial;
		private string m_endTag;
		private HWStack m_stack;
		private Node m_node; // current node (except for attributes)
		// Attributes are handled separately using these members.
		private Attribute m_a;
		private int m_apos; // which attribute are we positioned on in the collection.
		private Uri m_baseUri;
		private StringBuilder m_sb;
		private StringBuilder m_name;
		private TextWriter m_log;
		private bool m_foundRoot;
		private bool m_ignoreDtd;

		// autoclose support
		private Node m_newnode;
		private int m_poptodepth;
		private int m_rootCount;
		private bool m_isHtml;
		private string m_rootElementName;

		private string m_href;
		private Entity m_lastError;
		private Func<Uri, StreamInformation> m_streamOpener;
		private TextReader m_inputStream;
		private string m_syslit;
		private string m_pubid;
		private string m_subset;
		private string m_docType;
		private bool m_whitespaceHandling;
		private CaseFolding m_folding = CaseFolding.None;
		private bool m_stripDocType = true;
		//private string m_startTag;
		private Dictionary<string, string> unknownNamespaces = new Dictionary<string, string>();

		/// <summary>
		/// Initialises a new instance of the SgmlReader class.
		/// </summary>
		public SgmlReader()
		{
			Init();
		}

		/// <summary>
		/// Initialises a new instance of the SgmlReader class with TextReader.
		/// </summary>
		/// <param name="tr">Initial content stream.</param>
		public SgmlReader(TextReader tr)
		{
			this.m_inputStream = tr;
			Init();
		}

		/// <summary>
		/// Initialises a new instance of the SgmlReader class with TextReader.
		/// </summary>
		/// <param name="tr">Initial content stream.</param>
		/// <param name="baseUri">Base uri</param>
		/// <param name="streamOpener">Stream opener delegate</param>
		public SgmlReader(TextReader tr, Uri baseUri, Func<Uri, StreamInformation> streamOpener)
		{
			this.m_inputStream = tr;
			this.m_baseUri = baseUri;
			this.m_streamOpener = streamOpener;
			Init();
		}

		/// <summary>
		/// Specify the SgmlDtd object directly.  This allows you to cache the Dtd and share
		/// it across multipl SgmlReaders.  To load a DTD from a URL use the SystemLiteral property.
		/// </summary>
		public SgmlDtd Dtd
		{
			get
			{
				if (this.m_dtd == null)
				{
					LazyLoadDtd(this.m_baseUri);
				}

				return this.m_dtd;
			}
			set
			{
				this.m_dtd = value;
			}
		}

		private void LazyLoadDtd(Uri baseUri)
		{
			if (this.m_dtd == null && !this.m_ignoreDtd)
			{
				if (string.IsNullOrEmpty(this.m_syslit))
				{
					if (this.m_docType != null && StringUtilities.EqualsIgnoreCase(this.m_docType, "html"))
					{
						var type = this.GetType();
						var name = type.Namespace + ".Html.dtd";
						using (var stm = type.Assembly.GetManifestResourceStream(name))
						{
							var sr = new StreamReader(stm);
							this.m_dtd = SgmlDtd.Parse(baseUri, "HTML", sr, null, this.m_streamOpener, null);
						}
					}
				}
				else
				{
					if (baseUri != null)
					{
						baseUri = new Uri(baseUri, this.m_syslit);
					}
					else if (this.m_baseUri != null)
					{
						baseUri = new Uri(this.m_baseUri, this.m_syslit);
					}
					else
					{
						baseUri = new Uri(this.m_syslit);
					}
					this.m_dtd = SgmlDtd.Parse(baseUri, this.m_docType, this.m_pubid, baseUri.AbsoluteUri, this.m_subset, this.m_streamOpener, null);
				}
			}

			if (this.m_dtd != null && this.m_dtd.Name != null)
			{
				switch (this.CaseFolding)
				{
					case CaseFolding.ToUpper:
						this.m_rootElementName = this.m_dtd.Name.ToUpperInvariant();
						break;
					case CaseFolding.ToLower:
						this.m_rootElementName = this.m_dtd.Name.ToLowerInvariant();
						break;
					default:
						this.m_rootElementName = this.m_dtd.Name;
						break;
				}

				this.m_isHtml = StringUtilities.EqualsIgnoreCase(this.m_dtd.Name, "html");
			}
		}

		/// <summary>
		/// The name of root element specified in the DOCTYPE tag.
		/// </summary>
		public string DocType
		{
			get
			{
				return this.m_docType;
			}
			set
			{
				this.m_docType = value;
			}
		}

		/// <summary>
		/// The root element of the document.
		/// </summary>
		public string RootElementName
		{
			get
			{
				return m_rootElementName;
			}
		}

		/// <summary>
		/// The PUBLIC identifier in the DOCTYPE tag
		/// </summary>
		public string PublicIdentifier
		{
			get
			{
				return this.m_pubid;
			}
			set
			{
				this.m_pubid = value;
			}
		}

		/// <summary>
		/// The SYSTEM literal in the DOCTYPE tag identifying the location of the DTD.
		/// </summary>
		public string SystemLiteral
		{
			get
			{
				return this.m_syslit;
			}
			set
			{
				this.m_syslit = value;
			}
		}

		/// <summary>
		/// The DTD internal subset in the DOCTYPE tag
		/// </summary>
		public string InternalSubset
		{
			get
			{
				return this.m_subset;
			}
			set
			{
				this.m_subset = value;
			}
		}

		/// <summary>
		/// The input stream containing SGML data to parse.
		/// You must specify this property or the Href property before calling Read().
		/// </summary>
		public TextReader InputStream
		{
			get
			{
				return this.m_inputStream;
			}
			set
			{
				this.m_inputStream = value;
				Init();
			}
		}

		/// <summary>
		/// Get/Set Stream opener delegate.
		/// </summary>
		/// <remarks>Call delegate by parser if require external resource.</remarks>
		public Func<Uri, StreamInformation> StreamOpener
		{
			get
			{
				return this.m_streamOpener;
			}
			set
			{
				this.m_streamOpener = value;
			}
		}

		/// <summary>
		/// The base Uri is used to resolve relative Uri's like the SystemLiteral and
		/// Href properties.  This is a method because BaseURI is a read-only
		/// property on the base XmlReader class.
		/// </summary>
		public void SetBaseUri(string uri)
		{
			this.m_baseUri = new Uri(uri);
		}

		/// <summary>
		/// Specify the location of the input SGML document as a URL.
		/// </summary>
		public string Href
		{
			get
			{
				return this.m_href;
			}
			set
			{
				this.m_href = value;
				Init();
				if (this.m_baseUri == null)
				{
					if (this.m_href.IndexOf("://") > 0)
					{
						this.m_baseUri = new Uri(this.m_href);
					}
				}
			}
		}

		/// <summary>
		/// Whether to strip out the DOCTYPE tag from the output (default true)
		/// </summary>
		public bool StripDocType
		{
			get
			{
				return this.m_stripDocType;
			}
			set
			{
				this.m_stripDocType = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to ignore any DTD reference.
		/// </summary>
		/// <value><c>true</c> if DTD references should be ignored; otherwise, <c>false</c>.</value>
		public bool IgnoreDtd
		{
			get { return m_ignoreDtd; }
			set { m_ignoreDtd = value; }
		}

		/// <summary>
		/// The case conversion behaviour while processing tags.
		/// </summary>
		public CaseFolding CaseFolding
		{
			get
			{
				return this.m_folding;
			}
			set
			{
				this.m_folding = value;
			}
		}

		/// <summary>
		/// DTD validation errors are written to this stream.
		/// </summary>
		public TextWriter ErrorLog
		{
			get
			{
				return this.m_log;
			}
			set
			{
				this.m_log = value;
			}
		}

		private void Log(string msg, params string[] args)
		{
			if (ErrorLog != null)
			{
				string err = string.Format(CultureInfo.CurrentUICulture, msg, args);
				if (this.m_lastError != this.m_current)
				{
					err = err + "    " + this.m_current.Context();
					this.m_lastError = this.m_current;
					ErrorLog.WriteLine("### Error:" + err);
				}
				else
				{
					string path = "";
					if (this.m_current.ResolvedUri != null)
					{
						path = this.m_current.ResolvedUri.AbsolutePath;
					}

					ErrorLog.WriteLine("### Error in {0}#{1}, line {2}, position {3}: {4}", path, this.m_current.Name, this.m_current.Line, this.m_current.LinePosition, err);
				}
			}
		}

		private void Log(string msg, char ch)
		{
			Log(msg, ch.ToString());
		}

		private void Init()
		{
			this.m_state = State.Initial;
			this.m_stack = new HWStack(10);
			this.m_node = Push(null, XmlNodeType.Document, null);
			this.m_node.IsEmpty = false;
			this.m_sb = new StringBuilder();
			this.m_name = new StringBuilder();
			this.m_poptodepth = 0;
			this.m_current = null;
			this.m_partial = '\0';
			this.m_endTag = null;
			this.m_a = null;
			this.m_apos = 0;
			this.m_newnode = null;
			this.m_rootCount = 0;
			this.m_foundRoot = false;
			this.unknownNamespaces.Clear();
		}

		private Node Push(string name, XmlNodeType nt, string value)
		{
			Node result = (Node)this.m_stack.Push();
			if (result == null)
			{
				result = new Node();
				this.m_stack[this.m_stack.Count - 1] = result;
			}

			result.Reset(name, nt, value);
			this.m_node = result;
			return result;
		}

		private void SwapTopNodes()
		{
			int top = this.m_stack.Count - 1;
			if (top > 0)
			{
				Node n = (Node)this.m_stack[top - 1];
				this.m_stack[top - 1] = this.m_stack[top];
				this.m_stack[top] = n;
			}
		}

		private Node Push(Node n)
		{
			// we have to do a deep clone of the Node object because
			// it is reused in the stack.
			Node n2 = Push(n.Name, n.NodeType, n.Value);
			n2.DtdType = n.DtdType;
			n2.IsEmpty = n.IsEmpty;
			n2.Space = n.Space;
			n2.XmlLang = n.XmlLang;
			n2.CurrentState = n.CurrentState;
			n2.CopyAttributes(n);
			this.m_node = n2;
			return n2;
		}

		private void Pop()
		{
			if (this.m_stack.Count > 1)
			{
				this.m_node = (Node)this.m_stack.Pop();
			}
		}

		private Node Top()
		{
			int top = this.m_stack.Count - 1;
			if (top > 0)
			{
				return (Node)this.m_stack[top];
			}

			return null;
		}

		/// <summary>
		/// The node type of the node currently being parsed.
		/// </summary>
		public override XmlNodeType NodeType
		{
			get
			{
				if (this.m_state == State.Attr)
				{
					return XmlNodeType.Attribute;
				}
				else if (this.m_state == State.AttrValue)
				{
					return XmlNodeType.Text;
				}
				else if (this.m_state == State.EndTag || this.m_state == State.AutoClose)
				{
					return XmlNodeType.EndElement;
				}

				return this.m_node.NodeType;
			}
		}

		/// <summary>
		/// The name of the current node, if currently positioned on a node or attribute.
		/// </summary>
		public override string Name
		{
			get
			{
				string result = null;
				if (this.m_state == State.Attr)
				{
					result = XmlConvert.EncodeName(this.m_a.Name);
				}
				else if (this.m_state != State.AttrValue)
				{
					result = this.m_node.Name;
				}

				return result;
			}
		}

		/// <summary>
		/// The local name of the current node, if currently positioned on a node or attribute.
		/// </summary>
		public override string LocalName
		{
			get
			{
				string result = Name;
				if (result != null)
				{
					int colon = result.IndexOf(':');
					if (colon != -1)
					{
						result = result.Substring(colon + 1);
					}
				}
				return result;
			}
		}

		/// <summary>
		/// The namespace of the current node, if currently positioned on a node or attribute.
		/// </summary>
		/// <remarks>
		/// If not positioned on a node or attribute, <see cref="UNDEFINED_NAMESPACE"/> is returned.
		/// </remarks>
		[SuppressMessage("Microsoft.Performance", "CA1820", Justification = "Cannot use IsNullOrEmpty in a switch statement and swapping the elegance of switch for a load of 'if's is not worth it.")]
		public override string NamespaceURI
		{
			get
			{
				// SGML has no namespaces, unless this turned out to be an xmlns attribute.
				if (this.m_state == State.Attr && string.Equals(this.m_a.Name, "xmlns", StringComparison.OrdinalIgnoreCase))
				{
					return "http://www.w3.org/2000/xmlns/";
				}

				string prefix = Prefix;
				switch (Prefix)
				{
					case "xmlns":
						return "http://www.w3.org/2000/xmlns/";
					case "xml":
						return "http://www.w3.org/XML/1998/namespace";
					case null: // Should never occur since Prefix never returns null
					case "":
						if (NodeType == XmlNodeType.Attribute)
						{
							// attributes without a prefix are never in any namespace
							return string.Empty;
						}
						else if (NodeType == XmlNodeType.Element)
						{
							// check if a 'xmlns:prefix' attribute is defined
							for (int i = this.m_stack.Count - 1; i > 0; --i)
							{
								Node node = this.m_stack[i] as Node;
								if ((node != null) && (node.NodeType == XmlNodeType.Element))
								{
									int index = node.GetAttribute("xmlns");
									if (index >= 0)
									{
										string value = node.GetAttribute(index).Value;
										if (value != null)
										{
											return value;
										}
									}
								}
							}
						}

						return string.Empty;
					default:
						{
							string value;
							if ((NodeType == XmlNodeType.Attribute) || (NodeType == XmlNodeType.Element))
							{

								// check if a 'xmlns:prefix' attribute is defined
								string key = "xmlns:" + prefix;
								for (int i = this.m_stack.Count - 1; i > 0; --i)
								{
									Node node = this.m_stack[i] as Node;
									if ((node != null) && (node.NodeType == XmlNodeType.Element))
									{
										int index = node.GetAttribute(key);
										if (index >= 0)
										{
											value = node.GetAttribute(index).Value;
											if (value != null)
											{
												return value;
											}
										}
									}
								}
							}

							// check if we've seen this prefix before
							if (!unknownNamespaces.TryGetValue(prefix, out value))
							{
								if (unknownNamespaces.Count > 0)
								{
									value = UNDEFINED_NAMESPACE + unknownNamespaces.Count.ToString();
								}
								else
								{
									value = UNDEFINED_NAMESPACE;
								}
								unknownNamespaces[prefix] = value;
							}
							return value;
						}
				}
			}
		}

		/// <summary>
		/// The prefix of the current node's name.
		/// </summary>
		public override string Prefix
		{
			get
			{
				string result = Name;
				if (result != null)
				{
					int colon = result.IndexOf(':');
					if (colon != -1)
					{
						result = result.Substring(0, colon);
					}
					else
					{
						result = string.Empty;
					}
				}
				return result ?? string.Empty;
			}
		}

		/// <summary>
		/// Whether the current node has a value or not.
		/// </summary>
		public override bool HasValue
		{
			get
			{
				if (this.m_state == State.Attr || this.m_state == State.AttrValue)
				{
					return true;
				}

				return (this.m_node.Value != null);
			}
		}

		/// <summary>
		/// The value of the current node.
		/// </summary>
		public override string Value
		{
			get
			{
				if (this.m_state == State.Attr || this.m_state == State.AttrValue)
				{
					return this.m_a.Value;
				}

				return this.m_node.Value;
			}
		}

		/// <summary>
		/// Gets the depth of the current node in the XML document.
		/// </summary>
		/// <value>The depth of the current node in the XML document.</value>
		public override int Depth
		{
			get
			{
				if (this.m_state == State.Attr)
				{
					return this.m_stack.Count;
				}
				else if (this.m_state == State.AttrValue)
				{
					return this.m_stack.Count + 1;
				}

				return this.m_stack.Count - 1;
			}
		}

		/// <summary>
		/// Gets the base URI of the current node.
		/// </summary>
		/// <value>The base URI of the current node.</value>
		public override string BaseURI
		{
			get
			{
				return this.m_baseUri == null ? "" : this.m_baseUri.AbsoluteUri;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the current node is an empty element (for example, &lt;MyElement/&gt;).
		/// </summary>
		public override bool IsEmptyElement
		{
			get
			{
				if (this.m_state == State.Markup || this.m_state == State.Attr || this.m_state == State.AttrValue)
				{
					return this.m_node.IsEmpty;
				}

				return false;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the current node is an attribute that was generated from the default value defined in the DTD or schema.
		/// </summary>
		/// <value>
		/// true if the current node is an attribute whose value was generated from the default value defined in the DTD or
		/// schema; false if the attribute value was explicitly set.
		/// </value>
		public override bool IsDefault
		{
			get
			{
				if (this.m_state == State.Attr || this.m_state == State.AttrValue)
					return this.m_a.IsDefault;

				return false;
			}
		}

		/// <summary>
		/// Gets the current xml:space scope.
		/// </summary>
		/// <value>One of the <see cref="XmlSpace"/> values. If no xml:space scope exists, this property defaults to XmlSpace.None.</value>
		public override XmlSpace XmlSpace
		{
			get
			{
				for (int i = this.m_stack.Count - 1; i > 1; i--)
				{
					Node n = (Node)this.m_stack[i];
					XmlSpace xs = n.Space;
					if (xs != XmlSpace.None)
						return xs;
				}

				return XmlSpace.None;
			}
		}

		/// <summary>
		/// Gets the current xml:lang scope.
		/// </summary>
		/// <value>The current xml:lang scope.</value>
		public override string XmlLang
		{
			get
			{
				for (int i = this.m_stack.Count - 1; i > 1; i--)
				{
					Node n = (Node)this.m_stack[i];
					string xmllang = n.XmlLang;
					if (xmllang != null)
						return xmllang;
				}

				return string.Empty;
			}
		}

		/// <summary>
		/// Specifies how white space is handled.
		/// </summary>
		public bool WhitespaceHandling
		{
			get
			{
				return this.m_whitespaceHandling;
			}
			set
			{
				this.m_whitespaceHandling = value;
			}
		}

		/// <summary>
		/// Gets the number of attributes on the current node.
		/// </summary>
		/// <value>The number of attributes on the current node.</value>
		public override int AttributeCount
		{
			get
			{
				if (this.m_state == State.Attr || this.m_state == State.AttrValue)
					//For compatibility with mono
					return this.m_node.AttributeCount;
				else if (this.m_node.NodeType == XmlNodeType.Element || this.m_node.NodeType == XmlNodeType.DocumentType)
					return this.m_node.AttributeCount;
				else
					return 0;
			}
		}

		/// <summary>
		/// Gets the value of an attribute with the specified <see cref="Name"/>.
		/// </summary>
		/// <param name="name">The name of the attribute to retrieve.</param>
		/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. </returns>
		public override string GetAttribute(string name)
		{
			if (this.m_state != State.Attr && this.m_state != State.AttrValue)
			{
				int i = this.m_node.GetAttribute(name);
				if (i >= 0)
					return GetAttribute(i);
			}

			return null;
		}

		/// <summary>
		/// Gets the value of the attribute with the specified <see cref="LocalName"/> and <see cref="NamespaceURI"/>.
		/// </summary>
		/// <param name="name">The local name of the attribute.</param>
		/// <param name="namespaceURI">The namespace URI of the attribute.</param>
		/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader.</returns>
		public override string GetAttribute(string name, string namespaceURI)
		{
			return GetAttribute(name); // SGML has no namespaces.
		}

		/// <summary>
		/// Gets the value of the attribute with the specified index.
		/// </summary>
		/// <param name="i">The index of the attribute.</param>
		/// <returns>The value of the specified attribute. This method does not move the reader.</returns>
		public override string GetAttribute(int i)
		{
			if (this.m_state != State.Attr && this.m_state != State.AttrValue)
			{
				Attribute a = this.m_node.GetAttribute(i);
				if (a != null)
					return a.Value;
			}

			throw new ArgumentOutOfRangeException("i");
		}

		/// <summary>
		/// Gets the value of the attribute with the specified index.
		/// </summary>
		/// <param name="i">The index of the attribute.</param>
		/// <returns>The value of the specified attribute. This method does not move the reader.</returns>
		public override string this[int i]
		{
			get
			{
				return GetAttribute(i);
			}
		}

		/// <summary>
		/// Gets the value of an attribute with the specified <see cref="Name"/>.
		/// </summary>
		/// <param name="name">The name of the attribute to retrieve.</param>
		/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. </returns>
		public override string this[string name]
		{
			get
			{
				return GetAttribute(name);
			}
		}

		/// <summary>
		/// Gets the value of the attribute with the specified <see cref="LocalName"/> and <see cref="NamespaceURI"/>.
		/// </summary>
		/// <param name="name">The local name of the attribute.</param>
		/// <param name="namespaceURI">The namespace URI of the attribute.</param>
		/// <returns>The value of the specified attribute. If the attribute is not found, a null reference (Nothing in Visual Basic) is returned. This method does not move the reader.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1023", Justification = "This design is that of Microsoft's XmlReader class and overriding its method is merely continuing the same design.")]
		public override string this[string name, string namespaceURI]
		{
			get
			{
				return GetAttribute(name, namespaceURI);
			}
		}

		/// <summary>
		/// Moves to the atttribute with the specified <see cref="Name"/>.
		/// </summary>
		/// <param name="name">The qualified name of the attribute.</param>
		/// <returns>true if the attribute is found; otherwise, false. If false, the reader's position does not change.</returns>
		public override bool MoveToAttribute(string name)
		{
			int i = this.m_node.GetAttribute(name);
			if (i >= 0)
			{
				MoveToAttribute(i);
				return true;
			}

			return false;
		}

		/// <summary>
		/// Moves to the attribute with the specified <see cref="LocalName"/> and <see cref="NamespaceURI"/>.
		/// </summary>
		/// <param name="name">The local name of the attribute.</param>
		/// <param name="ns">The namespace URI of the attribute.</param>
		/// <returns>true if the attribute is found; otherwise, false. If false, the reader's position does not change.</returns>
		public override bool MoveToAttribute(string name, string ns)
		{
			return MoveToAttribute(name);
		}

		/// <summary>
		/// Moves to the attribute with the specified index.
		/// </summary>
		/// <param name="i">The index of the attribute to move to.</param>
		public override void MoveToAttribute(int i)
		{
			Attribute a = this.m_node.GetAttribute(i);
			if (a != null)
			{
				this.m_apos = i;
				this.m_a = a;
				//Make sure that AttrValue does not overwrite the preserved value
				if (this.m_state != State.Attr && this.m_state != State.AttrValue)
				{
					this.m_node.CurrentState = this.m_state; //save current state.
				}

				this.m_state = State.Attr;
				return;
			}

			throw new ArgumentOutOfRangeException("i");
		}

		/// <summary>
		/// Moves to the first attribute.
		/// </summary>
		/// <returns></returns>
		public override bool MoveToFirstAttribute()
		{
			if (this.m_node.AttributeCount > 0)
			{
				MoveToAttribute(0);
				return true;
			}

			return false;
		}

		/// <summary>
		/// Moves to the next attribute.
		/// </summary>
		/// <returns>true if there is a next attribute; false if there are no more attributes.</returns>
		/// <remarks>
		/// If the current node is an element node, this method is equivalent to <see cref="MoveToFirstAttribute"/>. If <see cref="MoveToNextAttribute"/> returns true,
		/// the reader moves to the next attribute; otherwise, the position of the reader does not change.
		/// </remarks>
		public override bool MoveToNextAttribute()
		{
			if (this.m_state != State.Attr && this.m_state != State.AttrValue)
			{
				return MoveToFirstAttribute();
			}
			else if (this.m_apos < this.m_node.AttributeCount - 1)
			{
				MoveToAttribute(this.m_apos + 1);
				return true;
			}
			else
				return false;
		}

		/// <summary>
		/// Moves to the element that contains the current attribute node.
		/// </summary>
		/// <returns>
		/// true if the reader is positioned on an attribute (the reader moves to the element that owns the attribute); false if the reader is not positioned
		/// on an attribute (the position of the reader does not change).
		/// </returns>
		public override bool MoveToElement()
		{
			if (this.m_state == State.Attr || this.m_state == State.AttrValue)
			{
				this.m_state = this.m_node.CurrentState;
				this.m_a = null;
				return true;
			}
			else
				return (this.m_node.NodeType == XmlNodeType.Element);
		}

		/// <summary>
		/// Gets whether the content is HTML or not.
		/// </summary>
		public bool IsHtml
		{
			get
			{
				return this.m_isHtml;
			}
		}

		/// <summary>
		/// Returns the encoding of the current entity.
		/// </summary>
		/// <returns>The encoding of the current entity.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024", Justification = "This method to get the encoding does not simply read a value, but potentially causes significant processing of the input stream.")]
		public Encoding GetEncoding()
		{
			if (this.m_current == null)
			{
				OpenInput();
			}

			return this.m_current.Encoding;
		}

		private void OpenInput()
		{
			LazyLoadDtd(this.m_baseUri);

			if (this.Href != null)
			{
				this.m_current = new Entity("#document", null, this.m_href, this.m_streamOpener);
			}
			else if (this.m_inputStream != null)
			{
				this.m_current = new Entity("#document", null, this.m_inputStream, this.m_streamOpener);
			}
			else
			{
				throw new InvalidOperationException("You must specify input either via Href or InputStream properties");
			}

			this.m_current.IsHtml = this.IsHtml;
			this.m_current.Open(null, this.m_baseUri);
			if (this.m_current.ResolvedUri != null)
				this.m_baseUri = this.m_current.ResolvedUri;

			if (this.m_current.IsHtml && this.m_dtd == null)
			{
				this.m_docType = "HTML";
				LazyLoadDtd(this.m_baseUri);
			}
		}

		/// <summary>
		/// Reads the next node from the stream.
		/// </summary>
		/// <returns>true if the next node was read successfully; false if there are no more nodes to read.</returns>
		public override bool Read()
		{
			if (m_current == null)
			{
				OpenInput();
			}

			if (m_node.Simulated)
			{
				// return the next node
				m_node.Simulated = false;
				this.m_node = Top();
				this.m_state = this.m_node.CurrentState;
				return true;
			}

			bool foundnode = false;
			while (!foundnode)
			{
				switch (this.m_state)
				{
					case State.Initial:
						this.m_state = State.Markup;
						this.m_current.ReadChar();
						goto case State.Markup;
					case State.Eof:
						if (this.m_current.Parent != null)
						{
							this.m_current.Close();
							this.m_current = this.m_current.Parent;
						}
						else
						{
							return false;
						}
						break;
					case State.EndTag:
						if (string.Equals(this.m_endTag, this.m_node.Name, StringComparison.OrdinalIgnoreCase))
						{
							Pop(); // we're done!
							this.m_state = State.Markup;
							goto case State.Markup;
						}
						Pop(); // close one element
						foundnode = true;// return another end element.
						break;
					case State.Markup:
						if (this.m_node.IsEmpty)
						{
							Pop();
						}
						foundnode = ParseMarkup();
						break;
					case State.PartialTag:
						Pop(); // remove text node.
						this.m_state = State.Markup;
						foundnode = ParseTag(this.m_partial);
						break;
					case State.PseudoStartTag:
						foundnode = ParseStartTag('<');
						break;
					case State.AutoClose:
						Pop(); // close next node.
						if (this.m_stack.Count <= this.m_poptodepth)
						{
							this.m_state = State.Markup;
							if (this.m_newnode != null)
							{
								Push(this.m_newnode); // now we're ready to start the new node.
								this.m_newnode = null;
								this.m_state = State.Markup;
							}
							else if (this.m_node.NodeType == XmlNodeType.Document)
							{
								this.m_state = State.Eof;
								goto case State.Eof;
							}
						}
						foundnode = true;
						break;
					case State.CData:
						foundnode = ParseCData();
						break;
					case State.Attr:
						goto case State.AttrValue;
					case State.AttrValue:
						this.m_state = State.Markup;
						goto case State.Markup;
					case State.Text:
						Pop();
						goto case State.Markup;
					case State.PartialText:
						if (ParseText(this.m_current.Lastchar, false))
						{
							this.m_node.NodeType = XmlNodeType.Whitespace;
						}

						foundnode = true;
						break;
				}

				if (foundnode && this.m_node.NodeType == XmlNodeType.Whitespace && this.m_whitespaceHandling == false)
				{
					// strip out whitespace (caller is probably pretty printing the XML).
					foundnode = false;
				}
				if (!foundnode && this.m_state == State.Eof && this.m_stack.Count > 1)
				{
					this.m_poptodepth = 1;
					this.m_state = State.AutoClose;
					this.m_node = Top();
					return true;
				}
			}
			if (!m_foundRoot && (this.NodeType == XmlNodeType.Element ||
					this.NodeType == XmlNodeType.Text ||
					this.NodeType == XmlNodeType.CDATA))
			{
				m_foundRoot = true;
				if (this.IsHtml && (this.NodeType != XmlNodeType.Element ||
					!string.Equals(this.LocalName, "html", StringComparison.OrdinalIgnoreCase)))
				{
					// Simulate an HTML root element!
					this.m_node.CurrentState = this.m_state;
					Node root = Push("html", XmlNodeType.Element, null);
					SwapTopNodes(); // make html the outer element.
					this.m_node = root;
					root.Simulated = true;
					root.IsEmpty = false;
					this.m_state = State.Markup;
					//this.state = State.PseudoStartTag;
					//this.startTag = name;
				}

				return true;
			}

			return true;
		}

		private bool ParseMarkup()
		{
			char ch = this.m_current.Lastchar;
			if (ch == '<')
			{
				ch = this.m_current.ReadChar();
				return ParseTag(ch);
			}
			else if (ch != Entity.EOF)
			{
				if (this.m_node.DtdType != null && this.m_node.DtdType.ContentModel.DeclaredContent == DeclaredContent.CDATA)
				{
					// e.g. SCRIPT or STYLE tags which contain unparsed character data.
					this.m_partial = '\0';
					this.m_state = State.CData;
					return false;
				}
				else if (ParseText(ch, true))
				{
					this.m_node.NodeType = XmlNodeType.Whitespace;
				}

				return true;
			}

			this.m_state = State.Eof;
			return false;
		}

		private const string declterm = " \t\r\n><";
		private bool ParseTag(char ch)
		{
			if (ch == '%')
			{
				return ParseAspNet();
			}
			else if (ch == '!')
			{
				ch = this.m_current.ReadChar();
				if (ch == '-')
				{
					return ParseComment();
				}
				else if (ch == '[')
				{
					return ParseConditionalBlock();
				}
				else if (ch != '_' && !char.IsLetter(ch))
				{
					// perhaps it's one of those nasty office document hacks like '<![if ! ie ]>'
					string value = this.m_current.ScanToEnd(this.m_sb, "Recovering", ">"); // skip it
					Log("Ignoring invalid markup '<!" + value + ">");
					return false;
				}
				else
				{
					string name = this.m_current.ScanToken(this.m_sb, SgmlReader.declterm, false);
					if (string.Equals(name, "DOCTYPE", StringComparison.OrdinalIgnoreCase))
					{
						ParseDocType();

						// In SGML DOCTYPE SYSTEM attribute is optional, but in XML it is required,
						// therefore if there is no SYSTEM literal then add an empty one.
						if (this.GetAttribute("SYSTEM") == null && this.GetAttribute("PUBLIC") != null)
						{
							this.m_node.AddAttribute("SYSTEM", "", '"', this.m_folding == CaseFolding.None);
						}

						if (m_stripDocType)
						{
							return false;
						}
						else
						{
							this.m_node.NodeType = XmlNodeType.DocumentType;
							return true;
						}
					}
					else
					{
						Log("Invalid declaration '<!{0}...'.  Expecting '<!DOCTYPE' only.", name);
						this.m_current.ScanToEnd(null, "Recovering", ">"); // skip it
						return false;
					}
				}
			}
			else if (ch == '?')
			{
				this.m_current.ReadChar();// consume the '?' character.
				return ParsePI();
			}
			else if (ch == '/')
			{
				return ParseEndTag();
			}
			else
			{
				return ParseStartTag(ch);
			}
		}

		private string ScanName(string terminators)
		{
			string name = this.m_current.ScanToken(this.m_sb, terminators, false);
			switch (this.m_folding)
			{
				case CaseFolding.ToUpper:
					name = name.ToUpperInvariant();
					break;
				case CaseFolding.ToLower:
					name = name.ToLowerInvariant();
					break;
			}
			return name;
		}

		private static bool VerifyName(string name)
		{
			try
			{
				XmlConvert.VerifyName(name);
				return true;
			}
			catch (XmlException)
			{
				return false;
			}
		}

		private const string tagterm = " \t\r\n=/><";
		private const string aterm = " \t\r\n='\"/>";
		private const string avterm = " \t\r\n>";
		private bool ParseStartTag(char ch)
		{
			string name = null;
			if (m_state != State.PseudoStartTag)
			{
				if (SgmlReader.tagterm.IndexOf(ch) >= 0)
				{
					this.m_sb.Length = 0;
					this.m_sb.Append('<');
					this.m_state = State.PartialText;
					return false;
				}

				name = ScanName(SgmlReader.tagterm);
			}
			else
			{
				// TODO: Changes by mindtouch mean that  this.startTag is never non-null.  The effects of this need checking.

				//name = this.startTag;
				m_state = State.Markup;
			}

			Node n = Push(name, XmlNodeType.Element, null);
			n.IsEmpty = false;
			Validate(n);
			ch = this.m_current.SkipWhitespace();
			while (ch != Entity.EOF && ch != '>')
			{
				if (ch == '/')
				{
					n.IsEmpty = true;
					ch = this.m_current.ReadChar();
					if (ch != '>')
					{
						Log("Expected empty start tag '/>' sequence instead of '{0}'", ch);
						this.m_current.ScanToEnd(null, "Recovering", ">");
						return false;
					}
					break;
				}
				else if (ch == '<')
				{
					Log("Start tag '{0}' is missing '>'", name);
					break;
				}

				string aname = ScanName(SgmlReader.aterm);
				ch = this.m_current.SkipWhitespace();
				if (string.Equals(aname, ",", StringComparison.OrdinalIgnoreCase) ||
					string.Equals(aname, "=", StringComparison.OrdinalIgnoreCase) ||
					string.Equals(aname, ":", StringComparison.OrdinalIgnoreCase) ||
					string.Equals(aname, ";", StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}

				string value = null;
				char quote = '\0';
				if (ch == '=' || ch == '"' || ch == '\'')
				{
					if (ch == '=')
					{
						this.m_current.ReadChar();
						ch = this.m_current.SkipWhitespace();
					}

					if (ch == '\'' || ch == '\"')
					{
						quote = ch;
						value = ScanLiteral(this.m_sb, ch);
					}
					else if (ch != '>')
					{
						string term = SgmlReader.avterm;
						value = this.m_current.ScanToken(this.m_sb, term, false);
					}
				}

				if (ValidAttributeName(aname))
				{
					Attribute a = n.AddAttribute(aname, value ?? aname, quote, this.m_folding == CaseFolding.None);
					if (a == null)
					{
						Log("Duplicate attribute '{0}' ignored", aname);
					}
					else
					{
						ValidateAttribute(n, a);
					}
				}

				ch = this.m_current.SkipWhitespace();
			}

			if (ch == Entity.EOF)
			{
				this.m_current.Error("Unexpected EOF parsing start tag '{0}'", name);
			}
			else if (ch == '>')
			{
				this.m_current.ReadChar(); // consume '>'
			}

			if (this.Depth == 1)
			{
				if (this.m_rootCount == 1)
				{
					// Hmmm, we found another root level tag, soooo, the only
					// thing we can do to keep this a valid XML document is stop
					this.m_state = State.Eof;
					return false;
				}
				this.m_rootCount++;
			}

			ValidateContent(n);
			return true;
		}

		private bool ParseEndTag()
		{
			this.m_state = State.EndTag;
			this.m_current.ReadChar(); // consume '/' char.
			string name = this.ScanName(SgmlReader.tagterm);
			char ch = this.m_current.SkipWhitespace();
			if (ch != '>')
			{
				Log("Expected empty start tag '/>' sequence instead of '{0}'", ch);
				this.m_current.ScanToEnd(null, "Recovering", ">");
			}

			this.m_current.ReadChar(); // consume '>'

			this.m_endTag = name;

			// Make sure there's a matching start tag for it.                        
			bool caseInsensitive = (this.m_folding == CaseFolding.None);
			this.m_node = (Node)this.m_stack[this.m_stack.Count - 1];
			for (int i = this.m_stack.Count - 1; i > 0; i--)
			{
				Node n = (Node)this.m_stack[i];
				if (string.Equals(n.Name, name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
				{
					this.m_endTag = n.Name;
					return true;
				}
			}

			Log("No matching start tag for '</{0}>'", name);
			this.m_state = State.Markup;
			return false;
		}

		private bool ParseAspNet()
		{
			string value = "<%" + this.m_current.ScanToEnd(this.m_sb, "AspNet", "%>") + "%>";
			Push(null, XmlNodeType.CDATA, value);
			return true;
		}

		private bool ParseComment()
		{
			char ch = this.m_current.ReadChar();
			if (ch != '-')
			{
				Log("Expecting comment '<!--' but found {0}", ch);
				this.m_current.ScanToEnd(null, "Comment", ">");
				return false;
			}

			string value = this.m_current.ScanToEnd(this.m_sb, "Comment", "-->");

			// Make sure it's a valid comment!
			int i = value.IndexOf("--");

			while (i >= 0)
			{
				int j = i + 2;
				while (j < value.Length && value[j] == '-')
					j++;

				if (i > 0)
				{
					value = value.Substring(0, i - 1) + "-" + value.Substring(j);
				}
				else
				{
					value = "-" + value.Substring(j);
				}

				i = value.IndexOf("--");
			}

			if (value.Length > 0 && value[value.Length - 1] == '-')
			{
				value += " "; // '-' cannot be last character
			}

			Push(null, XmlNodeType.Comment, value);
			return true;
		}

		private const string cdataterm = "\t\r\n[]<>";
		private bool ParseConditionalBlock()
		{
			char ch = m_current.ReadChar(); // skip '['
			ch = m_current.SkipWhitespace();
			string name = m_current.ScanToken(m_sb, cdataterm, false);
			if (name.StartsWith("if "))
			{
				// 'downlevel-revealed' comment (another atrocity of the IE team)
				m_current.ScanToEnd(null, "CDATA", ">");
				return false;
			}
			else if (!string.Equals(name, "CDATA", StringComparison.OrdinalIgnoreCase))
			{
				Log("Expecting CDATA but found '{0}'", name);
				m_current.ScanToEnd(null, "CDATA", ">");
				return false;
			}
			else
			{
				ch = m_current.SkipWhitespace();
				if (ch != '[')
				{
					Log("Expecting '[' but found '{0}'", ch);
					m_current.ScanToEnd(null, "CDATA", ">");
					return false;
				}

				string value = m_current.ScanToEnd(m_sb, "CDATA", "]]>");

				Push(null, XmlNodeType.CDATA, value);
				return true;
			}
		}

		private const string dtterm = " \t\r\n>";
		private void ParseDocType()
		{
			char ch = this.m_current.SkipWhitespace();
			string name = this.ScanName(SgmlReader.dtterm);
			Push(name, XmlNodeType.DocumentType, null);
			ch = this.m_current.SkipWhitespace();
			if (ch != '>')
			{
				string subset = "";
				string pubid = "";
				string syslit = "";

				if (ch != '[')
				{
					string token = this.m_current.ScanToken(this.m_sb, SgmlReader.dtterm, false);
					if (string.Equals(token, "PUBLIC", StringComparison.OrdinalIgnoreCase))
					{
						ch = this.m_current.SkipWhitespace();
						if (ch == '\"' || ch == '\'')
						{
							pubid = this.m_current.ScanLiteral(this.m_sb, ch);
							this.m_node.AddAttribute(token, pubid, ch, this.m_folding == CaseFolding.None);
						}
					}
					else if (!string.Equals(token, "SYSTEM", StringComparison.OrdinalIgnoreCase))
					{
						Log("Unexpected token in DOCTYPE '{0}'", token);
						this.m_current.ScanToEnd(null, "DOCTYPE", ">");
					}
					ch = this.m_current.SkipWhitespace();
					if (ch == '\"' || ch == '\'')
					{
						token = "SYSTEM";
						syslit = this.m_current.ScanLiteral(this.m_sb, ch);
						this.m_node.AddAttribute(token, syslit, ch, this.m_folding == CaseFolding.None);
					}
					ch = this.m_current.SkipWhitespace();
				}

				if (ch == '[')
				{
					subset = this.m_current.ScanToEnd(this.m_sb, "Internal Subset", "]");
					this.m_node.Value = subset;
				}

				ch = this.m_current.SkipWhitespace();
				if (ch != '>')
				{
					Log("Expecting end of DOCTYPE tag, but found '{0}'", ch);
					this.m_current.ScanToEnd(null, "DOCTYPE", ">");
				}

				if (this.m_dtd != null && !string.Equals(this.m_dtd.Name, name, StringComparison.OrdinalIgnoreCase))
				{
					throw new InvalidOperationException("DTD does not match document type");
				}

				this.m_docType = name;
				this.m_pubid = pubid;
				this.m_syslit = syslit;
				this.m_subset = subset;
				LazyLoadDtd(this.m_current.ResolvedUri);
			}

			this.m_current.ReadChar();
		}

		private const string piterm = " \t\r\n?";
		private bool ParsePI()
		{
			string name = this.m_current.ScanToken(this.m_sb, SgmlReader.piterm, false);
			string value = null;
			if (this.m_current.Lastchar != '?')
			{
				// Notice this is not "?>".  This is because Office generates bogus PI's that end with "/>".
				value = this.m_current.ScanToEnd(this.m_sb, "Processing Instruction", ">");
				value = value.TrimEnd('/');
			}
			else
			{
				// error recovery.
				value = this.m_current.ScanToEnd(this.m_sb, "Processing Instruction", ">");
			}

			// check if the name has a prefix; if so, ignore it
			int colon = name.IndexOf(':');
			if (colon > 0)
			{
				name = name.Substring(colon + 1);
			}

			// skip xml declarations, since these are generated in the output instead.
			if (!string.Equals(name, "xml", StringComparison.OrdinalIgnoreCase))
			{
				Push(name, XmlNodeType.ProcessingInstruction, value);
				return true;
			}

			return false;
		}

		private bool ParseText(char ch, bool newtext)
		{
			bool ws = !newtext || this.m_current.IsWhitespace;
			if (newtext)
				this.m_sb.Length = 0;

			//this.sb.Append(ch);
			//ch = this.current.ReadChar();
			this.m_state = State.Text;
			while (ch != Entity.EOF)
			{
				if (ch == '<')
				{
					ch = this.m_current.ReadChar();
					if (ch == '/' || ch == '!' || ch == '?' || char.IsLetter(ch))
					{
						// Hit a tag, so return XmlNodeType.Text token
						// and remember we partially started a new tag.
						this.m_state = State.PartialTag;
						this.m_partial = ch;
						break;
					}
					else
					{
						// not a tag, so just proceed.
						this.m_sb.Append('<');
						this.m_sb.Append(ch);
						ws = false;
						ch = this.m_current.ReadChar();
					}
				}
				else if (ch == '&')
				{
					ExpandEntity(this.m_sb, '<');
					ws = false;
					ch = this.m_current.Lastchar;
				}
				else
				{
					if (!this.m_current.IsWhitespace)
						ws = false;
					this.m_sb.Append(ch);
					ch = this.m_current.ReadChar();
				}
			}

			string value = this.m_sb.ToString();
			Push(null, XmlNodeType.Text, value);
			return ws;
		}

		/// <summary>
		/// Consumes and returns a literal block of text, expanding entities as it does so.
		/// </summary>
		/// <param name="sb">The string builder to use.</param>
		/// <param name="quote">The delimiter for the literal.</param>
		/// <returns>The consumed literal.</returns>
		/// <remarks>
		/// This version is slightly different from <see cref="Entity.ScanLiteral"/> in that
		/// it also expands entities.
		/// </remarks>
		private string ScanLiteral(StringBuilder sb, char quote)
		{
			sb.Length = 0;
			char ch = this.m_current.ReadChar();
			while (ch != Entity.EOF && ch != quote && ch != '>')
			{
				if (ch == '&')
				{
					ExpandEntity(sb, quote);
					ch = this.m_current.Lastchar;
				}
				else
				{
					sb.Append(ch);
					ch = this.m_current.ReadChar();
				}
			}
			if (ch == quote)
			{
				this.m_current.ReadChar(); // consume end quote.
			}
			return sb.ToString();
		}

		private bool ParseCData()
		{
			// Like ParseText(), only it doesn't allow elements in the content.  
			// It allows comments and processing instructions and text only and
			// text is not returned as text but CDATA (since it may contain angle brackets).
			// And initial whitespace is ignored.  It terminates when we hit the
			// end tag for the current CDATA node (e.g. </style>).
			bool ws = this.m_current.IsWhitespace;
			this.m_sb.Length = 0;
			char ch = this.m_current.Lastchar;
			if (this.m_partial != '\0')
			{
				Pop(); // pop the CDATA
				switch (this.m_partial)
				{
					case '!':
						this.m_partial = ' '; // and pop the comment next time around
						return ParseComment();
					case '?':
						this.m_partial = ' '; // and pop the PI next time around
						return ParsePI();
					case '/':
						this.m_state = State.EndTag;
						return true;    // we are done!
					case ' ':
						break; // means we just needed to pop the Comment, PI or CDATA.
				}
			}

			// if this.partial == '!' then parse the comment and return
			// if this.partial == '?' then parse the processing instruction and return.            
			while (ch != Entity.EOF)
			{
				if (ch == '<')
				{
					ch = this.m_current.ReadChar();
					if (ch == '!')
					{
						ch = this.m_current.ReadChar();
						if (ch == '-')
						{
							// return what CDATA we have accumulated so far
							// then parse the comment and return to here.
							if (ws)
							{
								this.m_partial = ' '; // pop comment next time through
								return ParseComment();
							}
							else
							{
								// return what we've accumulated so far then come
								// back in and parse the comment.
								this.m_partial = '!';
								break;
							}
#if FIX
						} else if (ch == '['){
							// We are about to wrap this node as a CDATA block because of it's
							// type in the DTD, but since we found a CDATA block in the input
							// we have to parse it as a CDATA block, otherwise we will attempt
							// to output nested CDATA blocks which of course is illegal.
							if (this.ParseConditionalBlock()){
								this.partial = ' ';
								return true;
							}
#endif
						}
						else
						{
							// not a comment, so ignore it and continue on.
							this.m_sb.Append('<');
							this.m_sb.Append('!');
							this.m_sb.Append(ch);
							ws = false;
						}
					}
					else if (ch == '?')
					{
						// processing instruction.
						this.m_current.ReadChar();// consume the '?' character.
						if (ws)
						{
							this.m_partial = ' '; // pop PI next time through
							return ParsePI();
						}
						else
						{
							this.m_partial = '?';
							break;
						}
					}
					else if (ch == '/')
					{
						// see if this is the end tag for this CDATA node.
						string temp = this.m_sb.ToString();
						if (ParseEndTag() && string.Equals(this.m_endTag, this.m_node.Name, StringComparison.OrdinalIgnoreCase))
						{
							if (ws || string.IsNullOrEmpty(temp))
							{
								// we are done!
								return true;
							}
							else
							{
								// return CDATA text then the end tag
								this.m_partial = '/';
								this.m_sb.Length = 0; // restore buffer!
								this.m_sb.Append(temp);
								this.m_state = State.CData;
								break;
							}
						}
						else
						{
							// wrong end tag, so continue on.
							this.m_sb.Length = 0; // restore buffer!
							this.m_sb.Append(temp);
							this.m_sb.Append("</" + this.m_endTag + ">");
							ws = false;

							// NOTE (steveb): we have one character in the buffer that we need to process next
							ch = this.m_current.Lastchar;
							continue;
						}
					}
					else
					{
						// must be just part of the CDATA block, so proceed.
						this.m_sb.Append('<');
						this.m_sb.Append(ch);
						ws = false;
					}
				}
				else
				{
					if (!this.m_current.IsWhitespace && ws)
						ws = false;
					this.m_sb.Append(ch);
				}

				ch = this.m_current.ReadChar();
			}

			// NOTE (steveb): check if we reached EOF, which means it's over
			if (ch == Entity.EOF)
			{
				this.m_state = State.Eof;
				return false;
			}

			string value = this.m_sb.ToString();

			// NOTE (steveb): replace any nested CDATA sections endings
			value = value.Replace("<![CDATA[", string.Empty);
			value = value.Replace("]]>", string.Empty);
			value = value.Replace("/**/", string.Empty);

			Push(null, XmlNodeType.CDATA, value);
			if (this.m_partial == '\0')
				this.m_partial = ' ';// force it to pop this CDATA next time in.

			return true;
		}

		private void ExpandEntity(StringBuilder sb, char terminator)
		{
			char ch = this.m_current.ReadChar();
			if (ch == '#')
			{
				string charent = this.m_current.ExpandCharEntity();
				sb.Append(charent);
				ch = this.m_current.Lastchar;
			}
			else
			{
				this.m_name.Length = 0;
				while (ch != Entity.EOF &&
					(char.IsLetter(ch) || ch == '_' || ch == '-') || ((this.m_name.Length > 0) && char.IsDigit(ch)))
				{
					this.m_name.Append(ch);
					ch = this.m_current.ReadChar();
				}
				string name = this.m_name.ToString();

				// TODO (steveb): don't lookup amp, gt, lt, quote
				switch (name)
				{
					case "amp":
						sb.Append("&");
						if (ch != terminator && ch != '&' && ch != Entity.EOF)
							ch = this.m_current.ReadChar();
						return;
					case "lt":
						sb.Append("<");
						if (ch != terminator && ch != '&' && ch != Entity.EOF)
							ch = this.m_current.ReadChar();
						return;
					case "gt":
						sb.Append(">");
						if (ch != terminator && ch != '&' && ch != Entity.EOF)
							ch = this.m_current.ReadChar();
						return;
					case "quot":
						sb.Append("\"");
						if (ch != terminator && ch != '&' && ch != Entity.EOF)
							ch = this.m_current.ReadChar();
						return;
					case "apos":
						sb.Append("'");
						if (ch != terminator && ch != '&' && ch != Entity.EOF)
							ch = this.m_current.ReadChar();
						return;
				}

				if (this.m_dtd != null && !string.IsNullOrEmpty(name))
				{
					Entity e = (Entity)this.m_dtd.FindEntity(name);
					if (e != null)
					{
						if (e.IsInternal)
						{
							sb.Append(e.Literal);
							if (ch != terminator && ch != '&' && ch != Entity.EOF)
								ch = this.m_current.ReadChar();

							return;
						}
						else
						{
							Entity ex = new Entity(name, e.PublicId, e.Uri, this.m_current.StreamOpener);
							e.Open(this.m_current, new Uri(e.Uri));
							this.m_current = ex;
							this.m_current.ReadChar();
							return;
						}
					}
					else
					{
						Log("Undefined entity '{0}'", name);
					}
				}
				// Entity is not defined, so just keep it in with the rest of the
				// text.
				sb.Append("&");
				sb.Append(name);
				if (ch != terminator && ch != '&' && ch != Entity.EOF)
				{
					sb.Append(ch);
					ch = this.m_current.ReadChar();
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether the reader is positioned at the end of the stream.
		/// </summary>
		/// <value>true if the reader is positioned at the end of the stream; otherwise, false.</value>
		public override bool EOF
		{
			get
			{
				return this.m_state == State.Eof;
			}
		}

		/// <summary>
		/// Changes the <see cref="ReadState"/> to Closed.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (this.m_current != null)
			{
				this.m_current.Close();
				this.m_current = null;
			}

			if (this.m_log != null)
			{
				this.m_log.Dispose();
				this.m_log = null;
			}

			base.Dispose(disposing);
		}

		/// <summary>
		/// Gets the state of the reader.
		/// </summary>
		/// <value>One of the ReadState values.</value>
		public override ReadState ReadState
		{
			get
			{
				if (this.m_state == State.Initial)
					return ReadState.Initial;
				else if (this.m_state == State.Eof)
					return ReadState.EndOfFile;
				else
					return ReadState.Interactive;
			}
		}

		/// <summary>
		/// Reads all the content, including markup, as a string.
		/// </summary>
		/// <returns>
		/// All the XML content, including markup, in the current node. If the current node has no children,
		/// an empty string is returned. If the current node is neither an element nor attribute, an empty
		/// string is returned.
		/// </returns>
		public override string ReadInnerXml()
		{
			StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
			XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings
			{
				Indent = true,
				IndentChars = "\t",
				ConformanceLevel = ConformanceLevel.Auto
			});
			switch (this.NodeType)
			{
				case XmlNodeType.Element:
					Read();
					while (!this.EOF && this.NodeType != XmlNodeType.EndElement)
					{
						xw.WriteNode(this, true);
					}
					Read(); // consume the end tag
					break;
				case XmlNodeType.Attribute:
					sw.Write(this.Value);
					break;
				default:
					// return empty string according to XmlReader spec.
					break;
			}

			xw.Flush();
			xw.Dispose();
			return sw.ToString();
		}

		/// <summary>
		/// Reads the content, including markup, representing this node and all its children.
		/// </summary>
		/// <returns>
		/// If the reader is positioned on an element or an attribute node, this method returns all the XML content, including markup, of the current node and all its children; otherwise, it returns an empty string.
		/// </returns>
		public override string ReadOuterXml()
		{
			StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
			XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings
			{
				Indent = true,
				IndentChars = "\t",
				ConformanceLevel = ConformanceLevel.Auto
			});
			xw.WriteNode(this, true);
			xw.Flush();
			xw.Dispose();
			return sw.ToString();
		}

		/// <summary>
		/// Gets the XmlNameTable associated with this implementation.
		/// </summary>
		/// <value>The XmlNameTable enabling you to get the atomized version of a string within the node.</value>
		public override XmlNameTable NameTable
		{
			get
			{
				return null;
			}
		}

		/// <summary>
		/// Resolves a namespace prefix in the current element's scope.
		/// </summary>
		/// <param name="prefix">The prefix whose namespace URI you want to resolve. To match the default namespace, pass an empty string.</param>
		/// <returns>The namespace URI to which the prefix maps or a null reference (Nothing in Visual Basic) if no matching prefix is found.</returns>
		public override string LookupNamespace(string prefix)
		{
			return null; // there are no namespaces in SGML.
		}

		/// <summary>
		/// Resolves the entity reference for EntityReference nodes.
		/// </summary>
		/// <exception cref="InvalidOperationException">SgmlReader does not resolve or return entities.</exception>
		public override void ResolveEntity()
		{
			// We never return any entity reference nodes, so this should never be called.
			throw new InvalidOperationException("Not on an entity reference.");
		}

		/// <summary>
		/// Parses the attribute value into one or more Text, EntityReference, or EndEntity nodes.
		/// </summary>
		/// <returns>
		/// true if there are nodes to return. false if the reader is not positioned on an attribute node when the initial call is made or if all the
		/// attribute values have been read. An empty attribute, such as, misc="", returns true with a single node with a value of string.Empty.
		/// </returns>
		public override bool ReadAttributeValue()
		{
			if (this.m_state == State.Attr)
			{
				this.m_state = State.AttrValue;
				return true;
			}
			else if (this.m_state == State.AttrValue)
			{
				return false;
			}
			else
				throw new InvalidOperationException("Not on an attribute.");
		}

		private void Validate(Node node)
		{
			if (this.m_dtd != null)
			{
				ElementDecl e = this.m_dtd.FindElement(node.Name);
				if (e != null)
				{
					node.DtdType = e;
					if (e.ContentModel.DeclaredContent == DeclaredContent.EMPTY)
						node.IsEmpty = true;
				}
			}
		}

		private static void ValidateAttribute(Node node, Attribute a)
		{
			ElementDecl e = node.DtdType;
			if (e != null)
			{
				AttDef ad = e.FindAttribute(a.Name);
				if (ad != null)
				{
					a.DtdType = ad;
				}
			}
		}

		private static bool ValidAttributeName(string name)
		{
			try
			{
				XmlConvert.VerifyNMTOKEN(name);
				int index = name.IndexOf(':');
				if (index >= 0)
				{
					XmlConvert.VerifyNCName(name.Substring(index + 1));
				}

				return true;
			}
			catch (XmlException)
			{
				return false;
			}
			catch (ArgumentNullException)
			{
				// (steveb) this is probably a bug in XmlConvert.VerifyNCName when passing in an empty string
				return false;
			}
		}

		private void ValidateContent(Node node)
		{
			if (node.NodeType == XmlNodeType.Element)
			{
				if (!VerifyName(node.Name))
				{
					Pop();
					Push(null, XmlNodeType.Text, "<" + node.Name + ">");
					return;
				}
			}

			if (this.m_dtd != null)
			{
				// See if this element is allowed inside the current element.
				// If it isn't, then auto-close elements until we find one
				// that it is allowed to be in.                                  
				string name = node.Name.ToUpperInvariant(); // DTD is in upper case
				int i = 0;
				int top = this.m_stack.Count - 2;
				if (node.DtdType != null)
				{
					// it is a known element, let's see if it's allowed in the
					// current context.
					for (i = top; i > 0; i--)
					{
						Node n = (Node)this.m_stack[i];
						if (n.IsEmpty)
							continue; // we'll have to pop this one
						ElementDecl f = n.DtdType;
						if (f != null)
						{
							if ((i == 2) && string.Equals(f.Name, "BODY", StringComparison.OrdinalIgnoreCase)) // NOTE (steveb): never close the BODY tag too early
								break;
							else if (string.Equals(f.Name, this.m_dtd.Name, StringComparison.OrdinalIgnoreCase))
								break; // can't pop the root element.
							else if (f.CanContain(name, this.m_dtd))
							{
								break;
							}
							else if (!f.EndTagOptional)
							{
								// If the end tag is not optional then we can't
								// auto-close it.  We'll just have to live with the
								// junk we've found and move on.
								break;
							}
						}
						else
						{
							// Since we don't understand this tag anyway,
							// we might as well allow this content!
							break;
						}
					}
				}

				if (i == 0)
				{
					// Tag was not found or is not allowed anywhere, ignore it and 
					// continue on.
					return;
				}
				else if (i < top)
				{
					Node n = (Node)this.m_stack[top];
					if (i == top - 1 && string.Equals(name, n.Name, StringComparison.OrdinalIgnoreCase))
					{
						// e.g. p not allowed inside p, not an interesting error.
					}
					else
					{
#if DEBUG
						string closing = "";
						for (int k = top; k >= i + 1; k--)
						{
							if (closing != "") closing += ",";
							Node n2 = (Node)this.m_stack[k];
							closing += "<" + n2.Name + ">";
						}
						Log("Element '{0}' not allowed inside '{1}', closing {2}.", name, n.Name, closing);
#endif
					}

					this.m_state = State.AutoClose;
					this.m_newnode = node;
					Pop(); // save this new node until we pop the others
					this.m_poptodepth = i + 1;
				}
			}
		}
	}
}